{{!

  Copyright 2016 Facebook, Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

}}{{!

Python wrappers of the structs defined in the services files. This file is
compiled into it's own module to be included by clients and services and
end-user code. It's one of the more complicated files, as it has to map
Pythonic APIs to C++ objects and back.

One of the nastier things in this file is the definition of containers.
A separate container wrapper has to be defined for each type of contained
attribute because Cython can't template C++ classes. So, for example, we need
a List__int16 or a List__string or a Map__string_mystruct instance for each
container/type combination. Consider that containers can contain other containers
or structs that contain containers and you realize how messy this can get.
Further, we'd prefer to have the end user freed from having to know about these
container types, so we'll need to define factories for them based on what they
want to include.

}}
{{> common/AutoGeneratedPy}}

cimport cython as __cython
from cpython.object cimport PyTypeObject
from libcpp.memory cimport shared_ptr, make_shared, unique_ptr, make_unique
from libcpp.string cimport string
from libcpp cimport bool as cbool
from libcpp.iterator cimport inserter as cinserter
from cpython cimport bool as pbool
from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, uint32_t
from cython.operator cimport dereference as deref, preincrement as inc, address as ptr_address
import thrift.py3.types
cimport thrift.py3.types
cimport thrift.py3.exceptions
from thrift.py3.types import NOTSET as __NOTSET
from thrift.py3.types cimport translate_cpp_enum_to_python, SetMetaClass as __SetMetaClass
cimport thrift.py3.std_libcpp as std_libcpp
from thrift.py3.serializer import Protocol as __Protocol
cimport thrift.py3.serializer as serializer
from thrift.py3.serializer import deserialize, serialize
import folly.iobuf as __iobuf
from folly.optional cimport cOptional

import sys
import itertools
from collections import Sequence, Set, Mapping, Iterable
import warnings
import builtins as _builtins
{{#program:includeNamespaces}}
{{#hasTypes?}}
cimport {{#includeNamespace}}{{value}}.{{/includeNamespace}}types as _{{#includeNamespace}}{{value}}_{{/includeNamespace}}types
import {{#includeNamespace}}{{value}}.{{/includeNamespace}}types as _{{#includeNamespace}}{{value}}_{{/includeNamespace}}types
{{/hasTypes?}}
{{/program:includeNamespaces}}
{{#program:extraNamespaces}}
cimport {{#extraNamespace}}{{value}}.{{/extraNamespace}}types as _{{#extraNamespace}}{{value}}_{{/extraNamespace}}types
import {{#extraNamespace}}{{value}}.{{/extraNamespace}}types as _{{#extraNamespace}}{{value}}_{{/extraNamespace}}types
{{/program:extraNamespaces}}

{{> types/enum }}
{{> types/unionTypeEnum }}

{{#program:structs}}
{{^struct:union?}}
{{#struct:plain?}}
cdef c{{struct:name}} _{{struct:name}}_defaults = c{{struct:name}}()

{{/struct:plain?}}
cdef class {{struct:name}}({{> types/PythonStructClass}}):

    {{^struct:exception?}}
    def __init__(
        {{struct:name}} self, *{{#struct:fields}},
        {{#field:type}}{{!
            }}{{^field:requireValue?}}{{!  # If we can set a None then type it
                }}{{#type:cythonTypeNoneable?}}{{!
                    }}{{> types/CythonPythonType}} {{!
                }}{{/type:cythonTypeNoneable?}}{{!
            }}{{/field:requireValue?}}{{!
            }}{{#field:requireValue?}}{{! We have no concept of default
                }}{{^type:integer?}}{{!
                    }}{{#type:hasCythonType?}}{{!
                        }}{{> types/CythonPythonType}} {{!
                    }}{{/type:hasCythonType?}}{{!
                }}{{/type:integer?}}{{!
            }}{{/field:requireValue?}}{{!
            }}{{field:name}}{{!
                }}{{^field:requireValue?}}=None{{/field:requireValue?}}{{!
                }}{{#field:requireValue?}}{{#type:Integers?}} not None{{/type:Integers?}}{{^type:number?}} not None{{/type:number?}}{{/field:requireValue?}}{{!
        }}{{/field:type}}{{/struct:fields}}
    ):
    {{/struct:exception?}}
    {{#struct:exception?}}{{! Python exceptions are weird about keyword only}}
    def __init__(
        {{struct:name}} self{{#struct:fields}},
        {{#field:type}}{{!
                }}{{#type:cythonTypeNoneable?}}{{!
                    }}{{> types/CythonPythonType}} {{!
                }}{{/type:cythonTypeNoneable?}}{{!
            }}{{field:name}}=None{{!
        }}{{/field:type}}{{/struct:fields}}
    ):
        {{#struct:fields}}
        {{#field:requireValue?}}
        if {{field:name}} is None:
            raise TypeError("__init__() needs required argument '{{field:name}}'")
        {{/field:requireValue?}}
        {{/struct:fields}}
    {{/struct:exception?}}
        {{#struct:fields}}
        {{#field:type}}
        {{^type:cythonTypeNoneable?}}
        {{^type:container?}}
        if {{field:name}} is not None:
            {{#type:number?}}
            if not isinstance({{field:name}}, {{> types/PythonIsInstanceType}}):
                raise TypeError(f'{{field:name}} is not a { {{> types/PythonType}} !r}.')
            {{#type:integer?}}
            {{! inject cython int overflow checks }}
            {{field:name}} = <{{> types/CythonPythonType}}> {{field:name}}
            {{/type:integer?}}
            {{/type:number?}}

        {{/type:container?}}
        {{/type:cythonTypeNoneable?}}
        {{/field:type}}
        {{/struct:fields}}
        self._cpp_obj = move({{struct:name}}._make_instance(
          NULL,{{#struct:fields}}
          {{field:name}},{{/struct:fields}}
        ))
        {{#struct:exception?}}
        _builtins.Exception.__init__(self, {{#struct:fields}}self.{{field:name}}{{^last?}}, {{/last?}}{{/struct:fields}})
        {{/struct:exception?}}

    {{^struct:exception?}}
    def __call__(
        {{struct:name}} self{{#struct:fields}},
        {{field:name}}=__NOTSET{{/struct:fields}}
    ):
        changes = any(({{#struct:fields}}
            {{field:name}} is not __NOTSET,
            {{/struct:fields}}
        ))

        if not changes:
            return self
        {{#struct:fields}}
        {{#first?}}

        {{/first?}}
        {{#field:requireValue?}}
        if {{field:name}} is None:
            raise TypeError('field {{field:name}} is required and has no default, it can not be unset')
        {{/field:requireValue?}}
        {{#field:type}}
        {{^type:container?}}
        if None is not {{field:name}} is not __NOTSET:
            {{#type:enum?}}
            if not isinstance({{field:name}}, {{> types/PythonType}}):
                raise TypeError(f'field {{field:name}} value: { {{field:name}} !r} is not of the enum type { {{> types/PythonType}} }.')
            {{/type:enum?}}
            {{^type:enum?}}
            if not isinstance({{field:name}}, {{> types/PythonIsInstanceType}}):
                raise TypeError(f'{{field:name}} is not a { {{> types/PythonType}} !r}.')
            {{#type:integer?}}
            {{! inject cython int overflow checks }}
            {{field:name}} = <{{> types/CythonPythonType}}> {{field:name}}
            {{/type:integer?}}
            {{/type:enum?}}

        {{/type:container?}}
        {{/field:type}}
        {{/struct:fields}}
        inst = <{{struct:name}}>{{struct:name}}.__new__({{struct:name}})
        inst._cpp_obj = move({{struct:name}}._make_instance(
          self._cpp_obj.get(),{{#struct:fields}}
          {{field:name}},{{/struct:fields}}
        ))
        return inst
    {{/struct:exception?}}

    @staticmethod
    cdef unique_ptr[c{{struct:name}}] _make_instance(
        c{{struct:name}}* base_instance{{#struct:fields}},
        object {{field:name}}{{/struct:fields}}
    ) except *:
        cdef unique_ptr[c{{struct:name}}] c_inst
        if base_instance:
            c_inst = make_unique[c{{struct:name}}](deref(base_instance))
        else:
            c_inst = make_unique[c{{struct:name}}]()

        {{! We never have a base_instance for exceptions (because they have
            no __call__, so don't generate this part for them because it
            references the _defaults object, which they also don't have )}}
        {{^struct:exception?}}
        if base_instance:
            # Convert None's to default value. (or unset)
            {{#struct:fields}}
            if {{field:name}} is None:
                {{#field:follyOptional?}}
                deref(c_inst).{{field:name}}.reset()
                {{/field:follyOptional?}}
                {{^field:follyOptional?}}
                {{#field:hasDefaultValue?}}
                deref(c_inst).{{field:name}} = _{{struct:name}}_defaults.{{field:name}}
                {{/field:hasDefaultValue?}}
                {{#field:isset?}}
                deref(c_inst).__isset.{{field:name}} = False
                {{/field:isset?}}
                {{/field:follyOptional?}}
                {{#field:reference?}}
                deref(c_inst).{{field:name}}.reset()
                {{/field:reference?}}
                pass
            elif {{field:name}} is __NOTSET:
                {{field:name}} = None

            {{/struct:fields}}{{!
            }}{{^struct:fields}}
            pass
            {{/struct:fields}}
        {{/struct:exception?}}
        {{#struct:fields}}{{#field:type}}
        if {{field:name}} is not None:
            {{> CythonAssignField}}

            {{! This should also be inside the if statement, so indent appropriately }}
            {{#field:isset?}}
            deref(c_inst).__isset.{{field:name}} = True
            {{/field:isset?}}
        {{/field:type}}{{/struct:fields}}
        # in C++ you don't have to call move(), but this doesn't translate
        # into a C++ return statement, so you do here
        return move_unique(c_inst)

    def __iter__(self):
        {{#struct:fields}}
        yield '{{field:name}}', self.{{field:name}}
        {{/struct:fields}}
        {{^struct:fields}}
        return iter(())
        {{/struct:fields}}

    def __bool__(self):
        return {{#struct:fields}}{{!
            }}{{#field:required?}}True{{/field:required?}}{{!
            }}{{^field:required?}}{{!
            }}{{#field:follyOptional?}}{{!
            }}deref(self._cpp_obj).{{field:name}}.has_value(){{!
            }}{{/field:follyOptional?}}{{!
            }}{{#field:hasDefaultValue?}}True{{/field:hasDefaultValue?}}{{!
            }}{{^field:follyOptional?}}{{!
            }}{{^field:hasDefaultValue?}}{{!
            }}{{#field:isset?}}deref(self._cpp_obj).__isset.{{field:name}}{{/field:isset?}}{{!
            }}{{#field:reference?}}<bint>(deref(self._cpp_obj).{{field:name}}){{/field:reference?}}{{!
            }}{{/field:hasDefaultValue?}}{{!
            }}{{/field:follyOptional?}}{{!
            }}{{/field:required?}}{{!
            }}{{^last?}} or {{/last?}}{{/struct:fields}}{{!
            }}{{^struct:fields}}True{{/struct:fields}}

    @staticmethod
    cdef create(shared_ptr[c{{struct:name}}] cpp_obj):
        inst = <{{struct:name}}>{{struct:name}}.__new__({{struct:name}}{{#struct:exception?}}, (<bytes>deref(cpp_obj).what()).decode('utf-8'){{/struct:exception?}})
        inst._cpp_obj = cpp_obj
        {{#struct:exception?}}
        _builtins.Exception.__init__(inst, {{#struct:fields}}inst.{{field:name}}{{^last?}}, {{/last?}}{{/struct:fields}})
        {{/struct:exception?}}
        return inst

    {{#struct:fields}}
    @property
    def {{field:name}}(self):
        {{#field:optional?}}
        {{#field:follyOptional?}}
        if not deref(self._cpp_obj).{{field:name}}.has_value():
            return None
        {{/field:follyOptional?}}
        {{^field:follyOptional?}}
        {{^field:hasDefaultValue?}}
        {{#field:isset?}}
        if not deref(self._cpp_obj).__isset.{{field:name}}:
            return None
        {{/field:isset?}}
        {{/field:hasDefaultValue?}}
        {{/field:follyOptional?}}
        {{/field:optional?}}
        {{#field:type}}

        {{> CythonStructGetter}}

        {{/field:type}}

    {{/struct:fields}}

    def __hash__({{struct:name}} self):
        {{^struct:exception?}}
        if not self.__hash:
            self.__hash = hash((
            {{#struct:fields}}
            self.{{field:name}},
            {{/struct:fields}}
            {{^struct:fields}}
            type(self)   # Hash the class there are no fields
            {{/struct:fields}}
            ))
        return self.__hash
        {{/struct:exception?}}
        {{#struct:exception?}}
        return super().__hash__()
        {{/struct:exception?}}

    def __repr__({{struct:name}} self):
        return f'{{struct:name}}({{#struct:fields}}{{field:name}}={repr(self.{{field:name}})}{{^last?}}, {{/last?}}{{/struct:fields}})'
{{/struct:union?}}{{!
}}{{#struct:union?}}


cdef class {{struct:name}}(thrift.py3.types.Union):
    Type = __{{struct:name}}Type

    def __init__(
        self, *{{#struct:fields}},
        {{#field:type}}{{!
            }}{{#type:cythonTypeNoneable?}}{{!
                }}{{> types/CythonPythonType}} {{!
            }}{{/type:cythonTypeNoneable?}}{{!
        }}{{/field:type}}{{!
            }}{{field:name}}=None{{!
        }}{{/struct:fields}}
    ):
        {{#struct:fields}}
        {{#field:type}}
        {{^type:cythonTypeNoneable?}}
        {{^type:container?}}
        if {{field:name}} is not None:
            if not isinstance({{field:name}}, {{> types/PythonIsInstanceType}}):
                raise TypeError(f'{{field:name}} is not a { {{> types/PythonType}} !r}.')
            {{#type:integer?}}
            {{! inject cython int overflow checks }}
            {{field:name}} = <{{> types/CythonPythonType}}> {{field:name}}
            {{/type:integer?}}

        {{/type:container?}}
        {{/type:cythonTypeNoneable?}}
        {{/field:type}}
        {{/struct:fields}}
        self._cpp_obj = move({{struct:name}}._make_instance(
          NULL,{{#struct:fields}}
          {{field:name}},{{/struct:fields}}
        ))
        self._load_cache()

    @staticmethod
    def fromValue(value):
        if value is None:
            return {{struct:name}}()
        {{! We do this with strict types first, then we will do int to float conversion}}
        {{#struct:fields}}
        {{#field:type}}
        if isinstance(value, {{> types/PythonType}}):
            {{#type:number?}}
            if not isinstance(value, pbool):
                try:
                    {{#type:integer?}}
                    {{! Cython does OverflowError checking for us }}
                    <{{> types/CythonPythonType}}> value
                    {{/type:integer?}}
                    {{#type:float?}}
                    {{! This will probably fail most of the time
                        if it does then when we try again to use floating point
                        below it will just accept the loss of precision,
                        or just maybe there is a double field comming up }}
                    if <{{> types/CythonPythonType}}>value != value:
                        raise OverflowError
                    {{/type:float?}}
                    return {{struct:name}}({{field:name}}=value)
                except OverflowError:
                    pass
            {{/type:number?}}
            {{^type:number?}}
            return {{struct:name}}({{field:name}}=value)
            {{/type:number?}}
        {{/field:type}}
        {{/struct:fields}}
        {{#struct:fields}}
        {{#field:type}}
        {{#type:floating_point?}}
        if isinstance(value, {{> types/PythonIsInstanceType}}):
            try:
                <{{> types/CythonPythonType}}> value
                return {{struct:name}}({{field:name}}=value)
            except OverflowError:
                pass
        {{/type:floating_point?}}
        {{/field:type}}
        {{/struct:fields}}
        raise ValueError(f"Unable to derive correct union field for value: {value}")

    @staticmethod
    cdef unique_ptr[c{{struct:name}}] _make_instance(
        c{{struct:name}}* base_instance{{#struct:fields}},
        {{field:name}}{{/struct:fields}}
    ) except *:
        cdef unique_ptr[c{{struct:name}}] c_inst = make_unique[c{{struct:name}}]()
        cdef bint any_set = False
        {{#struct:fields}}{{#field:type}}
        if {{field:name}} is not None:
            if any_set:
                raise TypeError("At most one field may be set when initializing a union")
            {{> CythonUnionAssignField}}

            any_set = True
        {{/field:type}}{{/struct:fields}}
        # in C++ you don't have to call move(), but this doesn't translate
        # into a C++ return statement, so you do here
        return move_unique(c_inst)

    def __bool__(self):
        return self.type is not __{{struct:name}}Type.EMPTY

    @staticmethod
    cdef create(shared_ptr[c{{struct:name}}] cpp_obj):
        inst = <{{struct:name}}>{{struct:name}}.__new__({{struct:name}})
        inst._cpp_obj = cpp_obj
        inst._load_cache()
        return inst

    {{#struct:fields}}
    @property
    def {{field:name}}(self):
        if self.type.value != {{field:key}}:
            raise TypeError(f'Union contains a value of type {self.type.name}, not {{field:name}}')
        return self.value

    {{/struct:fields}}

    def __hash__({{struct:name}} self):
        if not self.__hash:
            self.__hash = hash((
                self.type,
                self.value,
            ))
        return self.__hash

    def __repr__({{struct:name}} self):
        return f'{{struct:name}}(type={self.type.name}, value={self.value!r})'

    cdef _load_cache({{struct:name}} self):
        self.type = {{struct:name}}.Type(<int>(deref(self._cpp_obj).getType()))
        cdef int type = self.type.value
        if type == 0:    # Empty
            self.value = None
        {{#struct:fields}}
        elif type == {{field:key}}:
            {{#field:type}}
            {{> CythonUnionGetter}}
            {{/field:type}}

        {{/struct:fields}}

    def get_type({{struct:name}} self):
        return self.type

{{/struct:union?}}
    {{! Below are some things that are common to structs and unions: }}
    def __richcmp__(self, other, op):
        cdef int cop = op
        if cop not in (2, 3):
            raise TypeError("unorderable types: {}, {}".format(self, other))
        if not (
                isinstance(self, {{struct:name}}) and
                isinstance(other, {{struct:name}})):
            if cop == 2:  # different types are never equal
                return False
            else:         # different types are always notequal
                return True

        cdef c{{struct:name}} cself = deref((<{{struct:name}}>self)._cpp_obj)
        cdef c{{struct:name}} cother = deref((<{{struct:name}}>other)._cpp_obj)
        cdef cbool cmp = cself == cother
        if cop == 2:
            return cmp
        return not cmp

    {{^struct:exception?}}
    cdef __iobuf.IOBuf _serialize({{struct:name}} self, proto):
        cdef __iobuf.cIOBufQueue queue = __iobuf.cIOBufQueue(__iobuf.cacheChainLength())
        cdef c{{struct:name}}* cpp_obj = self._cpp_obj.get()
        if proto is __Protocol.COMPACT:
            with nogil:
                serializer.CompactSerialize[c{{struct:name}}](deref(cpp_obj), &queue, serializer.SHARE_EXTERNAL_BUFFER)
        elif proto is __Protocol.BINARY:
            with nogil:
                serializer.BinarySerialize[c{{struct:name}}](deref(cpp_obj), &queue, serializer.SHARE_EXTERNAL_BUFFER)
        elif proto is __Protocol.JSON:
            with nogil:
                serializer.JSONSerialize[c{{struct:name}}](deref(cpp_obj), &queue, serializer.SHARE_EXTERNAL_BUFFER)
        return __iobuf.from_unique_ptr(queue.move())

    cdef uint32_t _deserialize({{struct:name}} self, const __iobuf.cIOBuf* buf, proto) except? 0:
        cdef uint32_t needed
        {{!
            This is a special case, we need to construct an empty _cpp_obj because
            thrift.py3.serializer.deserialize will just call __new__ to skip
            all of our runtime type checks. We do it like this because
            thrift.py3.serializer.deserialize does not have enough type information
            to call the staticmethod .create()
        }}
        self._cpp_obj = make_shared[c{{struct:name}}]()
        cdef c{{struct:name}}* cpp_obj = self._cpp_obj.get()
        if proto is __Protocol.COMPACT:
            with nogil:
                needed = serializer.CompactDeserialize[c{{struct:name}}](buf, deref(cpp_obj), serializer.SHARE_EXTERNAL_BUFFER)
        elif proto is __Protocol.BINARY:
            with nogil:
                needed = serializer.BinaryDeserialize[c{{struct:name}}](buf, deref(cpp_obj), serializer.SHARE_EXTERNAL_BUFFER)
        elif proto is __Protocol.JSON:
            with nogil:
                needed = serializer.JSONDeserialize[c{{struct:name}}](buf, deref(cpp_obj), serializer.SHARE_EXTERNAL_BUFFER)
        {{#struct:union?}}
        # force a cache reload since the underlying data's changed
        self._load_cache()
        {{/struct:union?}}
        return needed

    def __reduce__(self):
        return (deserialize, ({{struct:name}}, serialize(self)))
    {{/struct:exception?}}


{{/program:structs}}
{{#program:containerTypes}}
cdef class {{type:flat_name}}:
    def __init__(self, items=None):
        if isinstance(items, {{type:flat_name}}):
            self._cpp_obj = (<{{type:flat_name}}> items)._cpp_obj
        else:
            self._cpp_obj = move({{type:flat_name}}._make_instance(items))

    @staticmethod
    cdef create(shared_ptr[{{> types/CythonCppType}}] c_items):
        inst = <{{type:flat_name}}>{{type:flat_name}}.__new__({{type:flat_name}})
        inst._cpp_obj = c_items
        return inst

{{#type:list?}}
    @staticmethod
    cdef unique_ptr[{{> types/CythonCppType}}] _make_instance(object items) except *:
        cdef unique_ptr[{{> types/CythonCppType}}] c_inst = make_unique[{{> types/CythonCppType}}]()
        if items is not None:
            for item in items:
                {{#type:listElemType}}
                {{#type:container?}}
                if item is None:
                    raise TypeError("None is not of the type {{> types/PEP484Type}}")
                if not isinstance(item, {{> types/PythonType}}):
                    item = {{> types/PythonType}}(item)
                {{/type:container?}}
                {{^type:container?}}
                if not isinstance(item, {{> types/PythonIsInstanceType}}):
                    raise TypeError(f"{item!r} is not of type {{> types/PEP484Type}}")
                {{#type:integer?}}
                {{! inject cython int overflow checks }}
                item = <{{> types/CythonPythonType}}> item
                {{/type:integer?}}
                {{/type:container?}}
                deref(c_inst).push_back({{> CythonPythonToCppItem}})
                {{/type:listElemType}}
        return move_unique(c_inst)

    def __add__(object self, object other):
        return type(self)(itertools.chain(self, other))

    def __getitem__(self, object index_obj):
        cdef shared_ptr[{{> types/CythonCppType}}] c_inst
        cdef {{#type:listElemType}}{{> types/CythonCppType}}{{/type:listElemType}} citem
        if isinstance(index_obj, slice):
            c_inst = make_shared[{{> types/CythonCppType}}]()
            sz = deref(self._cpp_obj).size()
            for index in range(*index_obj.indices(sz)):
                citem = deref(self._cpp_obj.get())[index]
                deref(c_inst).push_back(citem)
            return {{type:flat_name}}.create(c_inst)
        else:
            index = <int?>index_obj
            size = len(self)
            # Convert a negative index
            if index < 0:
                index = size + index
            if index >= size or index < 0:
                raise IndexError('list index out of range')
            {{#type:listElemType}}
            citem = deref(self._cpp_obj.get())[index]
            return {{> ContainerCythonCppToPythonItem}}
            {{/type:listElemType}}

    def __len__(self):
        return deref(self._cpp_obj).size()

    def __richcmp__(self, other, op):
        cdef int cop = op
        if cop not in (2, 3):
            raise TypeError("unorderable types: {}, {}".format(type(self), type(other)))
        if not (isinstance(self, Iterable) and isinstance(other, Iterable)):
            return cop != 2
        if (len(self) != len(other)):
            return cop != 2

        for one, two in zip(self, other):
            if one != two:
                return cop != 2

        return cop == 2

    def __hash__(self):
        if not self.__hash:
            self.__hash = hash(tuple(self))
        return self.__hash

    def __contains__(self, item):
        if not self or item is None:
            return False
        {{#type:listElemType}}
        {{#type:container?}}
        try:
            if not isinstance(item, {{> types/PythonType}}):
                item = {{> types/PythonType}}(item)
        except Exception:
            return False
        {{/type:container?}}
        if not isinstance(item, {{> types/PythonType}}):
            return False
        cdef {{> types/CythonCppType}} citem = {{> CythonPythonToCppItem}}
        {{/type:listElemType}}
        cdef {{> types/CythonCppType}} vec = deref(
            self._cpp_obj.get())
        return std_libcpp.find(vec.begin(), vec.end(), citem) != vec.end()

    def __iter__(self):
        if not self:
            raise StopIteration
        cdef {{#type:listElemType}}{{!
            }}{{>types/CythonCppType}}{{!
            }}{{/type:listElemType}} citem
        for citem in deref(self._cpp_obj):
            yield {{#type:listElemType}}{{!
                }}{{> ContainerCythonCppToPythonItem}}{{!
                }}{{/type:listElemType}}

    def __repr__(self):
        if not self:
            return 'i[]'
        return f'i[{", ".join(map(repr, self))}]'

    def __reversed__(self):
        if not self:
            raise StopIteration
        cdef {{#type:listElemType}}{{!
            }}{{>types/CythonCppType}}{{!
            }}{{/type:listElemType}} citem
        cdef {{> types/CythonCppType}} vec = deref(
            self._cpp_obj.get())
        cdef {{>types/CythonCppType}}.reverse_iterator loc = vec.rbegin()
        while loc != vec.rend():
            citem = deref(loc)
            yield {{#type:listElemType}}{{!
                }}{{> ContainerCythonCppToPythonItem}}{{!
                }}{{/type:listElemType}}
            inc(loc)

    def index(self, item, start not None=__NOTSET, stop not None=__NOTSET):
        err = ValueError(f'{item} is not in list')
        if not self or item is None:
            raise err
        offset_begin = offset_end = 0
        if stop is not __NOTSET or start is not __NOTSET:
            # Like self[start:stop].index(item)
            size = len(self)
            stop = stop if stop is not __NOTSET else size
            start = start if start is not __NOTSET else 0
            # Convert stop to a negative position.
            if stop > 0:
                stop = min(stop - size, 0)
            if stop <= -size:
                raise err  # List would be empty
            offset_end = -stop
            # Convert start to always be positive
            if start < 0:
                start = max(size + start, 0)
            if start >= size:
                raise err  # past end of list
            offset_begin = start

        {{#type:listElemType}}
        {{#type:container?}}
        try:
            if not isinstance(item, {{> types/PythonType}}):
                item = {{> types/PythonType}}(item)
        except Exception:
            raise err from None
        {{/type:container?}}
        if not isinstance(item, {{> types/PythonType}}):
            raise err
        cdef {{> types/CythonCppType}} citem = {{> CythonPythonToCppItem}}
        {{/type:listElemType}}
        cdef {{> types/CythonCppType}} vec = {{!
            }}deref(self._cpp_obj.get())
        cdef {{>types/CythonCppType}}.iterator end = std_libcpp.prev(vec.end(), <int64_t>offset_end)
        cdef {{>types/CythonCppType}}.iterator loc = {{!
            }}std_libcpp.find(
            std_libcpp.next(vec.begin(), <int64_t>offset_begin),
            end,
            citem
        )
        if loc != end:
            return <int64_t> std_libcpp.distance(vec.begin(), loc)
        raise err

    def count(self, item):
        if not self or item is None:
            return 0
        {{#type:listElemType}}
        {{#type:container?}}
        try:
            if not isinstance(item, {{> types/PythonType}}):
                item = {{> types/PythonType}}(item)
        except Exception:
            return 0
        {{/type:container?}}
        if not isinstance(item, {{> types/PythonType}}):
            return 0
        cdef {{> types/CythonCppType}} citem = {{> CythonPythonToCppItem}}
        {{/type:listElemType}}
        cdef {{> types/CythonCppType}} vec = {{!
            }}deref(self._cpp_obj.get())
        return <int64_t> std_libcpp.count(vec.begin(), vec.end(), citem)


Sequence.register({{type:flat_name}})
{{/type:list?}}
{{#type:set?}}
    @staticmethod
    cdef unique_ptr[{{> types/CythonCppType}}] _make_instance(object items) except *:
        cdef unique_ptr[{{> types/CythonCppType}}] c_inst = make_unique[{{> types/CythonCppType}}]()
        if items is not None:
            for item in items:
                {{#type:setElemType}}
                {{#type:container?}}
                if item is None:
                    raise TypeError("None is not of type {{> types/PEP484Type}}")
                if not isinstance(item, {{> types/PythonType}}):
                    item = {{> types/PythonType}}(item)
                {{/type:container?}}
                {{^type:container?}}
                if not isinstance(item, {{> types/PythonIsInstanceType}}):
                    raise TypeError(f"{item!r} is not of type {{> types/PEP484Type}}")
                {{#type:integer?}}
                {{! inject cython int overflow checks }}
                item = <{{> types/CythonPythonType}}> item
                {{/type:integer?}}
                {{/type:container?}}
                deref(c_inst).insert({{> CythonPythonToCppItem}})
                {{/type:setElemType}}
        return move_unique(c_inst)

    def __contains__(self, item):
        if not self or item is None:
            return False
        {{#type:setElemType}}
        {{#type:container?}}
        try:
            if not isinstance(item, {{> types/PythonType}}):
                item = {{> types/PythonType}}(item)
        except Exception:
            return False
        {{/type:container?}}
        if not isinstance(item, {{> types/PythonType}}):
            return False
        return pbool(deref(self._cpp_obj).count({{>CythonPythonToCppItem}}))
        {{/type:setElemType}}


    def __len__(self):
        return deref(self._cpp_obj).size()

    def __iter__(self):
        if not self:
            raise StopIteration
        for citem in deref(self._cpp_obj):
            yield {{#type:setElemType}}{{> ContainerCythonCppToPythonItem}}{{/type:setElemType}}

    def __repr__(self):
    {{=<% %>=}}
        if not self:
            return 'iset()'
        return f'i{{{", ".join(map(repr, self))}}}'
    <%={{ }}=%>

    def __richcmp__(self, other, op):
        cdef int cop = op
        cdef {{> types/CythonCppType}} cself, cother
        cdef cbool retval
        if (isinstance(self, {{type:flat_name}}) and
                isinstance(other, {{type:flat_name}})):
            cself = deref((<{{type:flat_name}}> self)._cpp_obj)
            cother = deref((<{{type:flat_name}}> other)._cpp_obj)
            # C level comparisons
            if cop == 0:    # Less Than (strict subset)
                if not cself.size() < cother.size():
                    return False
                for item in cself:
                    if not cother.count(item):
                        return False
                return True
            elif cop == 1:  # Less Than or Equal To  (subset)
                for item in cself:
                    if not cother.count(item):
                        return False
                return True
            elif cop == 2:  # Equivalent
                if cself.size() != cother.size():
                    return False
                for item in cself:
                    if not cother.count(item):
                        return False
                return True
            elif cop == 3:  # Not Equivalent
                for item in cself:
                    if not cother.count(item):
                        return True
                return cself.size() != cother.size()
            elif cop == 4:  # Greater Than (strict superset)
                if not cself.size() > cother.size():
                    return False
                for item in cother:
                    if not cself.count(item):
                        return False
                return True
            elif cop == 5:  # Greater Than or Equal To (superset)
                for item in cother:
                    if not cself.count(item):
                        return False
                return True

        # Python level comparisons
        if cop == 0:
            return Set.__lt__(self, other)
        elif cop == 1:
            return Set.__le__(self, other)
        elif cop == 2:
            return Set.__eq__(self, other)
        elif cop == 3:
            return Set.__ne__(self, other)
        elif cop == 4:
            return Set.__gt__(self, other)
        elif cop == 5:
            return Set.__ge__(self, other)

    def __hash__(self):
        if not self.__hash:
            self.__hash = hash(tuple(self))
        return self.__hash

    def __and__(self, other):
        if not isinstance(self, {{type:flat_name}}):
            self = {{type:flat_name}}(self)
        if not isinstance(other, {{type:flat_name}}):
            other = {{type:flat_name}}(other)

        cdef shared_ptr[{{> types/CythonCppType}}] shretval = \
            make_shared[{{> types/CythonCppType}}]()
        for citem in deref((<{{type:flat_name}}> self)._cpp_obj):
            if deref((<{{type:flat_name}}> other)._cpp_obj).count(citem) > 0:
                deref(shretval).insert(citem)
        return {{type:flat_name}}.create(shretval)

    def __sub__(self, other):
        if not isinstance(self, {{type:flat_name}}):
            self = {{type:flat_name}}(self)
        if not isinstance(other, {{type:flat_name}}):
            other = {{type:flat_name}}(other)

        cdef shared_ptr[{{> types/CythonCppType}}] shretval = \
            make_shared[{{> types/CythonCppType}}]()
        for citem in deref((<{{type:flat_name}}> self)._cpp_obj):
            if deref((<{{type:flat_name}}> other)._cpp_obj).count(citem) == 0:
                deref(shretval).insert(citem)
        return {{type:flat_name}}.create(shretval)

    def __or__(self, other):
        if not isinstance(self, {{type:flat_name}}):
            self = {{type:flat_name}}(self)
        if not isinstance(other, {{type:flat_name}}):
            other = {{type:flat_name}}(other)

        cdef shared_ptr[{{> types/CythonCppType}}] shretval = \
            make_shared[{{> types/CythonCppType}}]()
        for citem in deref((<{{type:flat_name}}> self)._cpp_obj):
                deref(shretval).insert(citem)
        for citem in deref((<{{type:flat_name}}> other)._cpp_obj):
                deref(shretval).insert(citem)
        return {{type:flat_name}}.create(shretval)

    def __xor__(self, other):
        if not isinstance(self, {{type:flat_name}}):
            self = {{type:flat_name}}(self)
        if not isinstance(other, {{type:flat_name}}):
            other = {{type:flat_name}}(other)

        cdef shared_ptr[{{> types/CythonCppType}}] shretval = \
            make_shared[{{> types/CythonCppType}}]()
        for citem in deref((<{{type:flat_name}}> self)._cpp_obj):
            if deref((<{{type:flat_name}}> other)._cpp_obj).count(citem) == 0:
                deref(shretval).insert(citem)
        for citem in deref((<{{type:flat_name}}> other)._cpp_obj):
            if deref((<{{type:flat_name}}> self)._cpp_obj).count(citem) == 0:
                deref(shretval).insert(citem)
        return {{type:flat_name}}.create(shretval)

    def isdisjoint(self, other):
        return len(self & other) == 0

    def union(self, other):
        return self | other

    def intersection(self, other):
        return self & other

    def difference(self, other):
        return self - other

    def symmetric_difference(self, other):
        return self ^ other

    def issubset(self, other):
        return self <= other

    def issuperset(self, other):
        return self >= other


Set.register({{type:flat_name}})
{{/type:set?}}
{{#type:map?}}
    @staticmethod
    cdef unique_ptr[{{> types/CythonCppType}}] _make_instance(object items) except *:
        cdef unique_ptr[{{> types/CythonCppType}}] c_inst = make_unique[{{> types/CythonCppType}}]()
        if items is not None:
            for key, item in items.items():
                {{#type:keyType}}
                {{#type:container?}}
                if key is None:
                    raise TypeError("None is not of type {{> types/PEP484Type}}")
                if not isinstance(key, {{> types/PythonType}}):
                    key = {{> types/PythonType}}(key)
                {{/type:container?}}
                {{^type:container?}}
                if not isinstance(key, {{> types/PythonIsInstanceType}}):
                    raise TypeError(f"{key!r} is not of type {{> types/PEP484Type}}")
                {{#type:integer?}}
                {{! inject cython int overflow checks }}
                key = <{{> types/CythonPythonType}}> key
                {{/type:integer?}}
                {{/type:container?}}
                {{/type:keyType}}
                {{#type:valueType}}
                {{#type:container?}}
                if item is None:
                    raise TypeError("None is not of type {{> types/PEP484Type}}")
                if not isinstance(item, {{> types/PythonType}}):
                    item = {{> types/PythonType}}(item)
                {{/type:container?}}
                {{^type:container?}}
                if not isinstance(item, {{> types/PythonIsInstanceType}}):
                    raise TypeError(f"{item!r} is not of type {{> types/PEP484Type}}")
                {{#type:integer?}}
                {{! inject cython int overflow checks }}
                item = <{{> types/CythonPythonType}}> item
                {{/type:integer?}}
                {{/type:container?}}
                {{/type:valueType}}

                deref(c_inst).insert(cpair[{{!
                    }}{{#type:keyType}}{{> types/CythonCppType}}{{/type:keyType}},{{!
                    }}{{#type:valueType}}{{> types/CythonCppType}}{{/type:valueType}}{{!
                    }}]({{!
                    }}{{#type:keyType}}{{> CythonPythonToCppKey}}{{/type:keyType}},{{!
                    }}{{#type:valueType}}{{> CythonPythonToCppItem}}{{/type:valueType}}))
        return move_unique(c_inst)

    def __getitem__(self, key):
        err = KeyError(f'{key}')
        if not self or key is None:
            raise err
        {{#type:keyType}}
        {{#type:container?}}
        try:
            if not isinstance(key, {{> types/PythonType}}):
                key = {{> types/PythonType}}(key)
        except Exception:
            raise err from None
        {{/type:container?}}
        if not isinstance(key, {{> types/PythonType}}):
            raise err
        cdef {{> types/CythonCppType}} ckey = {{> CythonPythonToCppKey}}
        {{/type:keyType}}
        cdef {{> types/CythonCppType}}.iterator iter = deref(
            self._cpp_obj).find(ckey)
        if iter == deref(self._cpp_obj).end():
            raise err
        cdef {{#type:valueType}}{{!
            }}{{> types/CythonCppType}} {{!
            }}citem = deref(iter).second{{/type:valueType}}
        return {{#type:valueType}}{{!
            }}{{> ContainerCythonCppToPythonItem}}{{/type:valueType}}

    def __len__(self):
        return deref(self._cpp_obj).size()

    def __iter__(self):
        if not self:
            raise StopIteration
        cdef {{#type:keyType}}{{!
            }}{{> types/CythonCppType}}{{/type:keyType}} citem
        for pair in deref(self._cpp_obj):
            citem = pair.first
            yield {{#type:keyType}}{{!
                }}{{> ContainerCythonCppToPythonItem}}{{/type:keyType}}

    def __richcmp__(self, other, op):
        cdef int cop = op
        if cop not in (2, 3):
            raise TypeError("unorderable types: {}, {}".format(type(self), type(other)))
        if not (isinstance(self, Mapping) and isinstance(other, Mapping)):
            return cop != 2
        if (len(self) != len(other)):
            return cop != 2

        for key in self:
            if key not in other:
                return cop != 2
            if other[key] != self[key]:
                return cop != 2

        return cop == 2

    def __hash__(self):
        if not self.__hash:
            self.__hash = hash(tuple(self.items()))
        return self.__hash

    def __repr__(self):
    {{=<% %>=}}
        if not self:
            return 'i{}'
        return f'i{{{", ".join(map(lambda i: f"{repr(i[0])}: {repr(i[1])}", self.items()))}}}'
    <%={{ }}=%>

    def __contains__(self, key):
        if not self or key is None:
            return False
        {{#type:keyType}}
        {{#type:container?}}
        try:
            if not isinstance(key, {{> types/PythonType}}):
                key = {{> types/PythonType}}(key)
        except Exception:
            return False
        {{/type:container?}}
        if not isinstance(key, {{> types/PythonType}}):
            return False
        cdef {{> types/CythonCppType}} ckey = {{> CythonPythonToCppKey}}
        {{/type:keyType}}
        return deref(self._cpp_obj).count(ckey) > 0

    def get(self, key, default=None):
        if not self or key is None:
            return default
        {{! Convert the key once }}
        {{#type:keyType}}
        try:
            if not isinstance(key, {{> types/PythonType}}):
                key = {{> types/PythonType}}(key)
        except Exception:
            return default
        if not isinstance(key, {{> types/PythonType}}):
            return default
        {{/type:keyType}}
        if key not in self:
            return default
        return self[key]

    def keys(self):
        return self.__iter__()

    def values(self):
        if not self:
            raise StopIteration
        cdef {{#type:valueType}}{{!
          }}{{> types/CythonCppType}}{{!
          }}{{/type:valueType}}{{!!
          }} citem
        for pair in deref(self._cpp_obj):
            citem = pair.second
            yield {{#type:valueType}}{{!
              }}{{> ContainerCythonCppToPythonItem}}{{!
              }}{{/type:valueType}}

    def items(self):
        if not self:
            raise StopIteration
        cdef {{#type:keyType}}{{!
          }}{{> types/CythonCppType}}{{!
          }}{{/type:keyType}}{{!
          }} ckey
        cdef {{#type:valueType}}{{!
          }}{{> types/CythonCppType}}{{!
          }}{{/type:valueType}} citem
        for pair in deref(self._cpp_obj):
            ckey = pair.first
            citem = pair.second

            yield ({{!
              }}{{#type:keyType}}{{!
              }}{{> ContainerCythonCppToPythonKey}}{{!
              }}{{/type:keyType}}, {{!
              }}{{#type:valueType}}{{!
              }}{{> ContainerCythonCppToPythonItem}}{{!
              }}{{/type:valueType}})



Mapping.register({{type:flat_name}})
{{/type:map?}}

{{/program:containerTypes}}{{!
}}{{#program:constants}}
{{#constant:value}}{{> ConstantValue}}{{/constant:value}}{{/program:constants}}{{!
}}{{#program:typedefs}}
{{typedef:symbolic}} = {{#typedef:type}}{{> types/PythonType}}{{/typedef:type}}
{{/program:typedefs}}
